package com.buttercoin.api; import com.buttercoin.api.functions.Status202ExtractLocation; import com.buttercoin.api.functions.Status204SuccessFailure; import com.buttercoin.api.functions.ThrowOnHttpFailure; import com.buttercoin.api.functions.MapResponseJson; import com.buttercoin.api.utils.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.AsyncHttpClientConfig; import com.ning.http.client.SignatureCalculator; import com.ning.http.client.extra.ThrottleRequestFilter; import com.ning.http.client.providers.jdk.JDKAsyncHttpProvider; import com.ning.http.client.providers.netty.NettyAsyncHttpProviderConfig; import java.math.BigDecimal; import java.net.URL; import java.time.Duration; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import static com.google.common.util.concurrent.Futures.transform; interface UnauthenticatedAPI { /** * Returns the current bid, ask, and last sell prices on Buttercoin. * * @return the current bid, ask, and last sell prices on Buttercoin */ ListenableFuture<Ticker> getTicker(); /** * Returns the current orders in the Buttercoin order book. * * @return the current orders in the Buttercoin order book */ ListenableFuture<OrderBook> getOrderBook(); /** * Returns the last 100 trades. * * @return the last 100 trades */ ListenableFuture<TradeHistory> getTradeHistory(); } interface AccountAPI { /** * Returns set of permissions associated with this account. * * @return permissions associated with this account */ ListenableFuture<Permissions> getKey(); /** * Returns set of permissions associated with this account. * * @return permissions associated with this account */ ListenableFuture<Permissions> getKey(long timestamp); /** * Returns balances for this account. * * @return balances for this account */ ListenableFuture<AccountBalances> getBalances(); /** * Returns balances for this account. * * @return balances for this account */ ListenableFuture<AccountBalances> getBalances(long timestamp); /** * Returns bitcoin address string to deposit your funds into the Buttercoin platform. * * @return bitcoin address string to deposit your funds into the Buttercoin platform */ ListenableFuture<DepositAddress> getDepositAddress(); /** * Returns bitcoin address string to deposit your funds into the Buttercoin platform. * * @return bitcoin address string to deposit your funds into the Buttercoin platform */ ListenableFuture<DepositAddress> getDepositAddress(long timestamp); } interface OrderAPI { ListenableFuture<Order> getOrder(String orderId); ListenableFuture<Order> getOrder(long timestamp, String orderId); ListenableFuture<Order> getOrder(URL url); ListenableFuture<Order> getOrder(long timestamp, URL url); ListenableFuture<Orders> getOrders(URL url); ListenableFuture<Orders> getOrders(long timestamp, URL url); ListenableFuture<Orders> getOrders(Order.Side side); ListenableFuture<Orders> getOrders(long timestamp, Order.Side side); ListenableFuture<Orders> getOrders(Order.Status... status); ListenableFuture<Orders> getOrders(long timestamp, Order.Status... status); ListenableFuture<Orders> getOrders(Order.OrderType... orderType); ListenableFuture<Orders> getOrders(long timestamp, Order.OrderType... orderType); ListenableFuture<Orders> getOrders(Order.Side side, Order.Status status, Order.OrderType orderType); ListenableFuture<Orders> getOrders(long timestamp, Order.Side side, Order.Status status, Order.OrderType orderType); ListenableFuture<Orders> getOrders(Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes); ListenableFuture<Orders> getOrders(long timestamp, Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes); ListenableFuture<Orders> getOrders(Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes, int page, int pageSize); ListenableFuture<Orders> getOrders(long timestamp, Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes, int page, int pageSize); ListenableFuture<Orders> getOrders(String... orderIds); ListenableFuture<Orders> getOrders(long timestamp, String... orderIds); ListenableFuture<URL> createOrder(CreateOrder.Instrument instrument, Order.Side side, Order.OrderType orderType, BigDecimal price, BigDecimal quantity); ListenableFuture<URL> createOrder(long timestamp, CreateOrder.Instrument instrument, Order.Side side, Order.OrderType orderType, BigDecimal price, BigDecimal quantity); ListenableFuture<Boolean> cancelOrder(String orderId); ListenableFuture<Boolean> cancelOrder(long timestamp, String orderId); } interface TransactionAPI { ListenableFuture<Transaction> getTransaction(String transactionId); ListenableFuture<Transaction> getTransaction(long timestamp, String transactionId); ListenableFuture<Transaction> getTransaction(URL url); ListenableFuture<Transaction> getTransaction(long timestamp, URL url); ListenableFuture<Transactions> getTransactions(URL url); ListenableFuture<Transactions> getTransactions(long timestamp, URL url); ListenableFuture<Transactions> getTransactions(Transaction.Status... status); ListenableFuture<Transactions> getTransactions(long timestamp, Transaction.Status... status); ListenableFuture<Transactions> getTransactions(Transaction.TransactionType... transactionType); ListenableFuture<Transactions> getTransactions(long timestamp, Transaction.TransactionType... transactionType); ListenableFuture<Transactions> getTransactions(Transaction.Status status, Transaction.TransactionType transactionType); ListenableFuture<Transactions> getTransactions(long timestamp, Transaction.Status status, Transaction.TransactionType transactionType); ListenableFuture<Transactions> getTransactions(Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes); ListenableFuture<Transactions> getTransactions(long timestamp, Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes); ListenableFuture<Transactions> getTransactions(Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes, int page, int pageSize); ListenableFuture<Transactions> getTransactions(long timestamp, Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes, int page, int pageSize); ListenableFuture<URL> createDeposit(String method, String currency, BigDecimal amount); ListenableFuture<URL> createDeposit(long timestamp, String method, String currency, BigDecimal amount); ListenableFuture<URL> createWithdrawal(String method, String currency, BigDecimal amount); ListenableFuture<URL> createWithdrawal(long timestamp, String method, String currency, BigDecimal amount); ListenableFuture<URL> sendBitcoin(BigDecimal amount, String destination); ListenableFuture<URL> sendBitcoin(long timestamp, BigDecimal amount, String destination); ListenableFuture<URL> sendBitcoin(String currency, BigDecimal amount, String destination); ListenableFuture<URL> sendBitcoin(long timestamp, String currency, BigDecimal amount, String destination); ListenableFuture<Boolean> cancelTransaction(String transactionId); ListenableFuture<Boolean> cancelTransaction(long timestamp, String transactionId); } public interface Buttercoin extends UnauthenticatedAPI, AccountAPI, OrderAPI, TransactionAPI { public static Builder newBuilder() { return new Builder(); } static final class Builder { private int throttleRequestFilterMaxConnections = 16; private int requestTimeout = 60 * 1000; private int idleTimeout = 60 * 1000; private boolean compressionEnforced = true; private ExecutorService executorService; private ObjectMapper objectMapper = new ObjectMapper(); private String apiKey; private String apiSecret; private String baseUrl = "https://api.buttercoin.com"; // specific to Async Http Client private boolean useJDKAsyncHttpProvider = false; private NettyAsyncHttpProviderConfig nettyAsyncHttpProviderConfig = null; public Builder() { } public Builder throttleRequestFilterMaxConnections(int throttleRequestFilterMaxConnections) { this.throttleRequestFilterMaxConnections = throttleRequestFilterMaxConnections; return this; } public Builder requestTimeout(Duration duration) { this.requestTimeout = (int) duration.toMillis(); return this; } public Builder requestTimeout(long timeout, TimeUnit unit) { this.requestTimeout = (int) unit.toMillis(timeout); return this; } public Builder idleTimeout(Duration duration) { this.idleTimeout = (int) duration.toMillis(); return this; } public Builder idleTimeout(long timeout, TimeUnit unit) { this.idleTimeout = (int) unit.toMillis(timeout); return this; } public Builder compressionEnforced(boolean compressionEnforced) { this.compressionEnforced = compressionEnforced; return this; } public Builder executorService(ExecutorService executorService) { Preconditions.checkNotNull(executorService); this.executorService = executorService; return this; } public Builder objectMapper(ObjectMapper objectMapper) { Preconditions.checkNotNull(objectMapper); this.objectMapper = objectMapper; return this; } public Builder apiKey(String apiKey) { Preconditions.checkNotNull(apiKey); Preconditions.checkArgument(apiKey.length() == 32, "Your Buttercoin API key seems invalid, length should be 32"); this.apiKey = apiKey; return this; } public Builder apiSecret(String apiSecret) { Preconditions.checkNotNull(apiSecret); Preconditions.checkArgument(apiSecret.length() == 32, "Your Buttercoin API secret seems invalid, length should be 32"); this.apiSecret = apiSecret; return this; } public Builder baseUrl(String baseUrl) { Preconditions.checkNotNull(baseUrl); if (baseUrl.endsWith("/")) throw new IllegalArgumentException("The baseUrl should not end with /"); this.baseUrl = baseUrl; return this; } public Builder useSandbox() { this.baseUrl = "https://sandbox.buttercoin.com"; return this; } public Builder useJDKAsyncHttpProvider(boolean useJDKAsyncHttpProvider) { this.useJDKAsyncHttpProvider = useJDKAsyncHttpProvider; return this; } public Builder nettyAsyncHttpProviderConfig(NettyAsyncHttpProviderConfig nettyAsyncHttpProviderConfig) { Preconditions.checkNotNull(nettyAsyncHttpProviderConfig); this.nettyAsyncHttpProviderConfig = nettyAsyncHttpProviderConfig; return this; } public Buttercoin build() { return new DefaultButtercoin(this); } } static final class DefaultButtercoin implements Buttercoin { private final AsyncHttpClient httpClient; private final ObjectMapper objectMapper; private final String baseUrl; private final String apiKey; private final String apiSecret; private final Supplier<SignatureCalculator> signatureCalculatorSupplier = new Supplier<SignatureCalculator>(){ public SignatureCalculator get(){ return new HMAC256SignatureCalculator( !Strings.isNullOrEmpty(apiKey) ? apiKey : System.getenv("BUTTERCOIN_API_KEY"), !Strings.isNullOrEmpty(apiSecret) ? apiSecret : System.getenv("BUTTERCOIN_API_SECRET")); } }; private volatile Supplier<SignatureCalculator> memorizedSignatureCalculator = Suppliers.memoize(signatureCalculatorSupplier); private DefaultButtercoin(Builder builder) { AsyncHttpClientConfig.Builder clientConfigBuilder = new AsyncHttpClientConfig.Builder(); clientConfigBuilder.addRequestFilter(new ThrottleRequestFilter(builder.throttleRequestFilterMaxConnections)); clientConfigBuilder.setRequestTimeout(builder.requestTimeout); clientConfigBuilder.setReadTimeout(builder.idleTimeout); clientConfigBuilder.setCompressionEnforced(builder.compressionEnforced); if (builder.executorService != null) clientConfigBuilder.setExecutorService(builder.executorService); clientConfigBuilder.setUserAgent("buttercoin-java/" + Buttercoin.class.getPackage().getImplementationVersion()); if (builder.useJDKAsyncHttpProvider) { httpClient = new AsyncHttpClient(new JDKAsyncHttpProvider(clientConfigBuilder.build())); } else { if (builder.nettyAsyncHttpProviderConfig != null) clientConfigBuilder.setAsyncHttpClientProviderConfig(builder.nettyAsyncHttpProviderConfig); httpClient = new AsyncHttpClient(clientConfigBuilder.build()); } objectMapper = builder.objectMapper; baseUrl = builder.baseUrl; apiKey = builder.apiKey; apiSecret = builder.apiSecret; } private SignatureCalculator signatureCalculator() { return memorizedSignatureCalculator.get(); } @Override public ListenableFuture<Ticker> getTicker() { return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/ticker") .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Ticker.class))); } @Override public ListenableFuture<OrderBook> getOrderBook() { return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/orderbook") .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(OrderBook.class))); } @Override public ListenableFuture<TradeHistory> getTradeHistory() { return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/trades") .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(TradeHistory.class))); } @Override public ListenableFuture<Permissions> getKey() { return getKey(System.currentTimeMillis()); } @Override public ListenableFuture<Permissions> getKey(long timestamp) { return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/key") .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Permissions.class))); } @Override public ListenableFuture<AccountBalances> getBalances() { return getBalances(System.currentTimeMillis()); } @Override public ListenableFuture<AccountBalances> getBalances(long timestamp) { return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/account/balances") .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(AccountBalances.class))); } @Override public ListenableFuture<DepositAddress> getDepositAddress() { return getDepositAddress(System.currentTimeMillis()); } @Override public ListenableFuture<DepositAddress> getDepositAddress(long timestamp) { return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/account/depositAddress") .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(DepositAddress.class))); } @Override public ListenableFuture<Order> getOrder(String orderId) { return getOrder(System.currentTimeMillis(), orderId); } @Override public ListenableFuture<Order> getOrder(long timestamp, String orderId) { Preconditions.checkNotNull(orderId, "orderId is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/orders/" + orderId) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Order.class))); } @Override public ListenableFuture<Order> getOrder(URL url) { return getOrder(System.currentTimeMillis(), url); } @Override public ListenableFuture<Order> getOrder(long timestamp, URL url) { Preconditions.checkNotNull(url, "order url is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(url.toString()) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Order.class))); } @Override public ListenableFuture<Orders> getOrders(URL url) { return getOrders(System.currentTimeMillis(), url); } @Override public ListenableFuture<Orders> getOrders(long timestamp, URL url) { Preconditions.checkNotNull(url, "orders url is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(url.toString()) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Orders.class))); } @Override public ListenableFuture<Orders> getOrders(Order.Side side) { return getOrders(System.currentTimeMillis(), side); } @Override public ListenableFuture<Orders> getOrders(long timestamp, Order.Side side) { return getOrders(timestamp, side, Lists.newArrayList(), Lists.newArrayList()); } @Override public ListenableFuture<Orders> getOrders(Order.Status... status) { return getOrders(System.currentTimeMillis(), status); } @Override public ListenableFuture<Orders> getOrders(long timestamp, Order.Status... status) { return getOrders(timestamp, null, Arrays.asList(status), null); } @Override public ListenableFuture<Orders> getOrders(Order.OrderType... orderType) { return getOrders(System.currentTimeMillis(), orderType); } @Override public ListenableFuture<Orders> getOrders(long timestamp, Order.OrderType... orderType) { return getOrders(timestamp, null, null, Arrays.asList(orderType)); } @Override public ListenableFuture<Orders> getOrders(Order.Side side, Order.Status status, Order.OrderType orderType) { return getOrders(System.currentTimeMillis(), side, status, orderType); } @Override public ListenableFuture<Orders> getOrders(long timestamp, Order.Side side, Order.Status status, Order.OrderType orderType) { return getOrders(timestamp, side, Lists.newArrayList(status), Lists.newArrayList(orderType)); } @Override public ListenableFuture<Orders> getOrders(Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes) { return getOrders(System.currentTimeMillis(), side, status, orderTypes); } @Override public ListenableFuture<Orders> getOrders(long timestamp, Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes) { return getOrders(timestamp, side, status, orderTypes, 0, 0); } @Override public ListenableFuture<Orders> getOrders(Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes, int page, int pageSize) { return getOrders(System.currentTimeMillis(), side, status, orderTypes, page, pageSize); } @Override public ListenableFuture<Orders> getOrders(long timestamp, Order.Side side, Iterable<Order.Status> status, Iterable<Order.OrderType> orderTypes, int page, int pageSize) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.prepareGet(baseUrl + "/v1/orders") .addHeader("X-Buttercoin-Date", "" + timestamp); if (side != null) builder.addQueryParam("side", side.getName()); if (status != null && status.iterator().hasNext()) builder.addQueryParam("status", mkString(status, ",")); if (orderTypes != null && orderTypes.iterator().hasNext()) builder.addQueryParam("orderType", mkString(orderTypes, ",")); if (page > 0) builder.addQueryParam("page", "" + page); if (pageSize > 0) builder.addQueryParam("pageSize", "" + pageSize); return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Orders.class))); } @Override public ListenableFuture<Orders> getOrders(String... orderIds) { return getOrders(System.currentTimeMillis(), orderIds); } @Override public ListenableFuture<Orders> getOrders(long timestamp, String... orderIds) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.prepareGet(baseUrl + "/v1/orders") .addHeader("X-Buttercoin-Date", "" + timestamp); builder.addQueryParam("id", mkString(Arrays.asList(orderIds), ",")); return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Orders.class))); } @Override public ListenableFuture<URL> createOrder(CreateOrder.Instrument instrument, Order.Side side, Order.OrderType orderType, BigDecimal price, BigDecimal quantity) { return createOrder(System.currentTimeMillis(), instrument, side, orderType, price, quantity); } @Override public ListenableFuture<URL> createOrder(long timestamp, CreateOrder.Instrument instrument, Order.Side side, Order.OrderType orderType, BigDecimal price, BigDecimal quantity) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.preparePost(baseUrl + "/v1/orders"); builder.addHeader("Content-Type", "application/json"); builder.addHeader("X-Buttercoin-Date", "" + timestamp); try { builder.setBody(objectMapper.writeValueAsString(new CreateOrder(instrument, side, orderType, price.setScale(2, BigDecimal.ROUND_HALF_DOWN).toString(), quantity.setScale(2, BigDecimal.ROUND_HALF_DOWN).toString()))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new Status202ExtractLocation()); } @Override public ListenableFuture<Boolean> cancelOrder(String orderId) { return cancelOrder(System.currentTimeMillis(), orderId); } @Override public ListenableFuture<Boolean> cancelOrder(long timestamp, String orderId) { return transform(new ListenableFutureAdapter<>(httpClient.prepareDelete(baseUrl + "/v1/orders/" + orderId) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new Status204SuccessFailure()); } @Override public ListenableFuture<Transaction> getTransaction(String transactionId) { return getTransaction(System.currentTimeMillis(), transactionId); } @Override public ListenableFuture<Transaction> getTransaction(long timestamp, String transactionId) { Preconditions.checkNotNull(transactionId, "transactionId is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(baseUrl + "/v1/transaction/" + transactionId) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Transaction.class))); } @Override public ListenableFuture<Transaction> getTransaction(URL url) { return getTransaction(System.currentTimeMillis(), url); } @Override public ListenableFuture<Transaction> getTransaction(long timestamp, URL url) { Preconditions.checkNotNull(url, "transaction url is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(url.toString()) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Transaction.class))); } @Override public ListenableFuture<Transactions> getTransactions(URL url) { return getTransactions(System.currentTimeMillis(), url); } @Override public ListenableFuture<Transactions> getTransactions(long timestamp, URL url) { Preconditions.checkNotNull(url, "transactions url is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareGet(url.toString()) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Transactions.class))); } @Override public ListenableFuture<Transactions> getTransactions(Transaction.Status... status) { return getTransactions(System.currentTimeMillis(), status); } @Override public ListenableFuture<Transactions> getTransactions(long timestamp, Transaction.Status... status) { return getTransactions(timestamp, Arrays.asList(status), null); } @Override public ListenableFuture<Transactions> getTransactions(Transaction.TransactionType... transactionType) { return getTransactions(System.currentTimeMillis(), transactionType); } @Override public ListenableFuture<Transactions> getTransactions(long timestamp, Transaction.TransactionType... transactionType) { return getTransactions(timestamp, null, Arrays.asList(transactionType)); } @Override public ListenableFuture<Transactions> getTransactions(Transaction.Status status, Transaction.TransactionType transactionType) { return getTransactions(System.currentTimeMillis(), status, transactionType); } @Override public ListenableFuture<Transactions> getTransactions(long timestamp, Transaction.Status status, Transaction.TransactionType transactionType) { return getTransactions(timestamp, Lists.newArrayList(status), Lists.newArrayList(transactionType)); } @Override public ListenableFuture<Transactions> getTransactions(Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes) { return getTransactions(System.currentTimeMillis(), status, transactionTypes); } @Override public ListenableFuture<Transactions> getTransactions(long timestamp, Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes) { return getTransactions(timestamp, status, transactionTypes, 0, 0); } @Override public ListenableFuture<Transactions> getTransactions(Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes, int page, int pageSize) { return getTransactions(System.currentTimeMillis(), status, transactionTypes, page, pageSize); } @Override public ListenableFuture<Transactions> getTransactions(long timestamp, Iterable<Transaction.Status> status, Iterable<Transaction.TransactionType> transactionTypes, int page, int pageSize) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.prepareGet(baseUrl + "/v1/transactions") .addHeader("X-Buttercoin-Date", "" + timestamp); if (status != null && status.iterator().hasNext()) builder.addQueryParam("status", mkString(status, ",")); if (transactionTypes != null && transactionTypes.iterator().hasNext()) builder.addQueryParam("transactionType", mkString(transactionTypes, ",")); if (page > 0) builder.addQueryParam("page", "" + page); if (pageSize > 0) builder.addQueryParam("pageSize", "" + pageSize); return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new MapResponseJson<>(objectMapper.reader(Transactions.class))); } @Override public ListenableFuture<URL> createDeposit(String method, String currency, BigDecimal amount) { return createDeposit(System.currentTimeMillis(), method, currency, amount); } @Override public ListenableFuture<URL> createDeposit(long timestamp, String method, String currency, BigDecimal amount) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.preparePost(baseUrl + "/v1/transactions/deposit"); builder.addHeader("Content-Type", "application/json"); builder.addHeader("X-Buttercoin-Date", "" + timestamp); try { builder.setBody(objectMapper.writeValueAsString(new CreateDeposit(method, currency, amount))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new Status202ExtractLocation()); } @Override public ListenableFuture<URL> createWithdrawal(String method, String currency, BigDecimal amount) { return createWithdrawal(System.currentTimeMillis(), method, currency, amount); } @Override public ListenableFuture<URL> createWithdrawal(long timestamp, String method, String currency, BigDecimal amount) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.preparePost(baseUrl + "/v1/transactions/withdraw"); builder.addHeader("Content-Type", "application/json"); builder.addHeader("X-Buttercoin-Date", "" + timestamp); try { builder.setBody(objectMapper.writeValueAsString(new CreateWithdrawal(method, currency, amount))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new Status202ExtractLocation()); } @Override public ListenableFuture<URL> sendBitcoin(BigDecimal amount, String destination) { return sendBitcoin(System.currentTimeMillis(), amount, destination); } @Override public ListenableFuture<URL> sendBitcoin(long timestamp, BigDecimal amount, String destination) { return sendBitcoin(timestamp, "BTC", amount, destination); } @Override public ListenableFuture<URL> sendBitcoin(String currency, BigDecimal amount, String destination) { return sendBitcoin(System.currentTimeMillis(), currency, amount, destination); } @Override public ListenableFuture<URL> sendBitcoin(long timestamp, String currency, BigDecimal amount, String destination) { AsyncHttpClient.BoundRequestBuilder builder = httpClient.preparePost(baseUrl + "/v1/transactions/send"); builder.addHeader("Content-Type", "application/json"); builder.addHeader("X-Buttercoin-Date", "" + timestamp); try { builder.setBody(objectMapper.writeValueAsString(new SendBitcoin(currency, amount, destination))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } return transform(new ListenableFutureAdapter<>(builder.setSignatureCalculator(signatureCalculator()).execute(new ThrowOnHttpFailure())), new Status202ExtractLocation()); } @Override public ListenableFuture<Boolean> cancelTransaction(String transactionId) { return cancelTransaction(System.currentTimeMillis(), transactionId); } @Override public ListenableFuture<Boolean> cancelTransaction(long timestamp, String transactionId) { Preconditions.checkNotNull(transactionId, "transactionId is null"); return transform(new ListenableFutureAdapter<>(httpClient.prepareDelete(baseUrl + "/v1/transactions/" + transactionId) .addHeader("X-Buttercoin-Date", "" + timestamp) .setSignatureCalculator(signatureCalculator()) .execute(new ThrowOnHttpFailure())), new Status204SuccessFailure()); } private static String mkString(Iterable<?> values, String sep) { if (values == null || !values.iterator().hasNext()) return ""; List<String> nonEmptyVals = new LinkedList<>(); for (Object val : values) { if (val != null && val.toString().trim().length() > 0) { nonEmptyVals.add(val.toString()); } } if (nonEmptyVals.size() == 0) return ""; StringBuilder result = new StringBuilder(); int i = 0; for (String val : nonEmptyVals) { if (i > 0) result.append(sep); result.append(val); i++; } return result.toString(); } } }